﻿ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Data;
using System.IO;
using System.Resources;
using System.Reflection;


namespace F3.Maps.OsmParser
{
    public class OsmParser
    {
        private XDocument document;
        private XElement root;
        private SpatialightDataAdapter dataAdapter;
        public DataTable dataTable { get { return ((SpatialightDataAdapter)dataAdapter).dataTable; } }
        
        SortedList<string, Point> sharedValues;
        IUpdater informer;

        public OsmParser(IUpdater informer, string connectionStirng, string tableName)
        {
            dataAdapter = new SpatialightDataAdapter(connectionStirng, tableName);
            
            this.informer = informer;
        }


        public void LoadOsmMap(string path)
        {
           
            this.document = XDocument.Load(path);
            this.root = this.document.FirstNode as XElement;
            var values = this.root.Elements("way")
                                  .Elements("nd")
                                  .GroupBy(i => i.Attribute("ref").Value)
                                  .Where(g => g.Count() > 1)
                                  .Select(g => g.Key).ToList();
            sharedValues = new SortedList<string, Point>();
            var totalnodes = values.Count();
            for(int index=0; index < totalnodes; index++)
            {
                var value = values[index];
                var point = new Node(FindElement(value, "node")).Point;
                sharedValues.Add(value, point);
                this.informer.update("Loading shared node " + index + " of " +  totalnodes, index, totalnodes);
            }
        }


        public void Process()
        {
            var highways = this.Highways().ToList();
            int total = highways.Count();
            dataAdapter.Begin();
            for (int index = 0; index < total; index++ )
            {
                var way = highways[index];
                Parse(way);
                this.informer.update("Parsing highway " + index + " of " + total, index++, total);
            }
            
            foreach (XElement way in highways)
            {
                
            }
            dataAdapter.End();
        
        }


        private void Parse(XElement highwayElement)
        {
            XElement referenceElement = highwayElement.Elements("nd").First();
            Point firstReference = this.FindPoint(referenceElement);
            this.Parse(new HighwayNode(highwayElement), firstReference, referenceElement);
        }


        private void Parse(HighwayNode highway, Point startingNode, XElement startReference)
        {
            StringBuilder geometry = new StringBuilder();
            geometry.Append("LINESTRING(" + startingNode.ToString());
            
            var points = startReference.ElementsAfterSelf("nd").ToList();
                         
            Point currentNode = null;            
            var totalPoints = points.Count(); 
            int pointsLeft = totalPoints;

            if (pointsLeft == 0)
            {
                return;
            }
            double distance = 0;
            XElement currentReference = null;
            
            Point previousNode = startingNode;
            Point previousNode2 = previousNode;

            foreach (var point in points)
            {
                bool isShared;

                currentReference = point;
                                
                var pointRef = point.Attribute("ref").Value.ToString();
                currentNode = this.FindPoint(pointRef, out isShared);
                geometry.Append(", " + currentNode.ToString());
                distance += GisUtility.CalculateDistance(previousNode, currentNode);

                previousNode2 = previousNode;
                previousNode = currentNode;
                pointsLeft--;
                
                if(isShared)
                {
                    break;   
                }
                
            }
                        
            string geometryString = geometry.Append(")").ToString();
            
            // Splitting the self intersecting roads.
            if(startingNode.ID == currentNode.ID)
            {
                if(totalPoints < 3)
                {
                    return;
                }
                pointsLeft++;

                var previousReference = points.ElementAt(totalPoints - 2);
                distance -= GisUtility.CalculateDistance(previousNode2, currentNode);
                geometryString = geometryString.Remove(geometryString.LastIndexOf(',')) + ")";

                currentReference = previousReference;
                currentNode = previousNode2;
            }
            // Replacing the 0 distance with a minor value
            if (double.IsNaN(distance) || distance == 0)
            {
                distance = 0.1;
                Log.Current.write(String.Format("Nan: Point 1: {0}, Point 2: {1}, Way: {2}", startingNode.ID, currentNode.ID, highway.ID));
            }

            this.dataAdapter.Save(highway, geometryString, startingNode.ID, currentNode.ID, distance);

            if (pointsLeft != 0)
            { 
                Parse(highway, currentNode, currentReference);
            }
        }

        /// <summary>
        /// Getting the highways
        /// </summary>
        /// <returns></returns>
        private IEnumerable<XElement> Highways()
        {
            var ways = this.root.Elements("way")
                                .Where((element) =>
                                {
                                    var highway = element.Elements("tag")
                                                         .Where((t) => t.Attribute("k").Value.ToString() == "highway")
                                                         .FirstOrDefault();
                                                      
                                    return highway != null;
                                });
            return ways;
        }

        /// <summary>
        /// Find Element 
        /// </summary>
        /// <param name="id"></param>
        /// <param name="nodeType"></param>
        /// <returns></returns>
        private XElement FindElement(string id, string nodeType)
        {
            var node = this.root
                           .Elements(nodeType)
                           .Where((n) =>
                                 {
                                    return n.Attribute("id").Value == id;
                                 })
                           .FirstOrDefault();

            return node;

        }

        /// <summary>
        /// Find Point
        /// </summary>
        /// <param name="id"></param>
        /// <param name="isShared"></param>
        /// <returns></returns>
        private Point FindPoint(string id, out bool isShared)
        {
            Point point;
            isShared = false;
            
            //var key = int.Parse(id);

            if (this.sharedValues.ContainsKey(id))
            {
                point = this.sharedValues[id];
                isShared = true;
            }
            else
            {
                point = new Node(FindElement(id, "node")).Point;                
            }
            
            return point;
        }

        /// <summary>
        /// Find Point
        /// </summary>
        /// <param name="nodeReference"></param>
        /// <returns></returns>
        private Point FindPoint(XElement nodeReference)
        {
            bool isShared;
            return FindPoint(nodeReference.Attribute("ref").Value.ToString(), out isShared);
        }


        

        
        
        
       

                
    }
}
